package edu.kit.csl.cratylus.extraction;

import java.util.List;

import edu.kit.csl.cratylus.datatypes.Candidate;
import edu.kit.csl.cratylus.datatypes.Sentence;
import edu.kit.csl.cratylus.datatypes.Word;
import edu.kit.csl.cratylus.ui.Configuration;

/**
 * This corpus updater aligns the found representative to the target sentence 
 * in order to enhance adjacent alignments. For example, if the first or last
 * position of the found representative don't match the phoneme sequence in the
 * corpus, it is likely that those phonemes actually belong to adjacent target
 * words, but they were wrongly aligned due to alignment errors. This corpus
 * updater aims to solve this kind of errors.
 */
public class AligningCorpusUpdater implements CorpusUpdater {
	final int maxWordExtension;
	
	/**
	 * Sole constructor. Fetches the maxWordExtension parameter.
	 */
	public AligningCorpusUpdater() {
		maxWordExtension = Configuration.getSingleton().getInteger(
				"maxWordExtension");
	}

	/* (non-Javadoc)
	 * @see CorpusUpdater#updateCorpus(Word, java.util.List)
	 */
	@Override
	public void updateCorpus(Word word, List<Candidate> candidates) {
		String[] refPhonemes = word.getContent().split(" ");
		if (refPhonemes[0].isEmpty()) {
			return;
		}
		String[] hypoPhonemes;
		for (Candidate candidate : candidates) {
			Sentence trgtSen = candidate.getPair().getTargetSentence();
			hypoPhonemes = candidate.getWord().getContent().split(" ");
			trgtSen.set(candidate.getPosition(), word);
			if (word.isNoise() || hypoPhonemes[0].isEmpty()) {
				continue;
			}
			evalMatrix(refPhonemes, hypoPhonemes, trgtSen,
					candidate.getPosition());
		}
	}
	
	/*
	 * Evaluates the distance matrix and modifies adjacent target words.
	 */
	private void evalMatrix(String[] refPhonemes, String[] hypoPhonemes,
			Sentence trgtSen, int pos) {
		int[][] mat = LevenshteinUtils.distanceMatrix(refPhonemes,
				hypoPhonemes);
		final int nRef = mat.length - 1;
		final int nHypo = mat[0].length - 1;
		if (trgtSen.getLength() > pos + 1 && !trgtSen.get(pos + 1).isFixed()) {
			Word nextWord = trgtSen.get(pos + 1);
			int n = 0;
			while (mat[nRef][nHypo - 1 - n] < mat[nRef][nHypo - n]) {
				n++;
			}
			if (n > 0 && n <= maxWordExtension) { // n del. at the end
				nextWord.setContent(rangeOfArray2String(hypoPhonemes,
						nHypo - n, n) + " " + nextWord.getContent());
			}
			n = 0;
			while (mat[nRef - 1 - n][nHypo] < mat[nRef - n][nHypo]) {
				n++;
			}
			if (n > 0) { // n insertions in reference at the end
				shrinkWord(nextWord, n, 0);
			}
		}
		if (pos > 0 && !trgtSen.get(pos - 1).isFixed()) {
			Word prevWord = trgtSen.get(pos - 1);
			// For ins/del in the beginning, backtrace the whole path.
			int r = nRef;
			int c = nHypo;
			do {
				if (mat[r][c] > mat[r - 1][c]) { // Deletion in ref
					r--;
				} else if (mat[r][c] > mat[r][c - 1]){ // Insertion in ref
					c--;
				} else { // Substitution or correspondence
					r--;
					c--;
				}
			} while (r * c > 0);
			if (r > 0) { // r deletions in reference in the beginning
				shrinkWord(prevWord, 0, r);
			}
			if (c > 0 && c <= maxWordExtension) { // c ins. in the beginning
				prevWord.setContent(prevWord.getContent() + " "
					+ rangeOfArray2String(hypoPhonemes, 0, c));
			}
		}
	}
	
	/*
	 * Similar to phps implode() function using blank as separator, but only
	 * implodes the array within a certain range. length must be greater than
	 * 0 and start a valid array index.
	 */
	private String rangeOfArray2String(String[] arr, int start, int length) {
		StringBuilder b = new StringBuilder();
		b.append(arr[start]);
		for (int i = 1; i < length; i++) {
			b.append(" " + arr[start + i]);
		}
		return b.toString();
	}
	
	/*
	 * Removes b phonemes from the beginning and e phonemes from the end of the
	 * word.
	 */
	private void shrinkWord(Word word, int b, int e) {
		String[] phonemes = word.getContent().split(" ");
		word.setContent(b + e >= phonemes.length ? "" :
				rangeOfArray2String(phonemes, b, phonemes.length - e - b));
	}
}
